/*
 * ﻿Copyright (C) 2012-2013 NewMain Softech
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package com.newmainsoftech.ant;

import java.lang.reflect.Method;
import java.util.Properties;

import org.apache.maven.project.MavenProject;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectComponent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Helper class to provide common access means to {@link MavenProject}}.
 * 
 * @author <a href="mailto:artymt@gmail.com">Arata Y.</a>
 */
public class MavenProjectAccessor {
// Intentionally avoided to make direct reference to MavenProject class (and other class from 
// org.apache.maven package) in this class in order to avoid encountering ClassNotFoundException 
// when used in stand-alone Ant environment. 
	
	Logger logger = LoggerFactory.getLogger( this.getClass());
		protected Logger getLogger() {
			return logger;
		}

	/**
	 * Reference ID for {@link MavenProject} from {@link Project}
	 */
	public static final String MavenProjectReferenceId = "maven.project";
		public static String getMavenProjectReferenceId() {
			return MavenProjectReferenceId;
		}

	/**
	 * The content is "${org.apache.maven.plugins:maven-antrun-plugin:jar}"
	 */
	public static final String MavenAntrunPluginArtifactJarDesignation  
	= "${org.apache.maven.plugins:maven-antrun-plugin:jar}";
		public static String getMavenAntrunPluginArtifactJarDesignation() {
			return MavenAntrunPluginArtifactJarDesignation;
		}
	public static final String MavenProjectClassName = "org.apache.maven.project.MavenProject";
		public static String getMavenProjectClassName() {
			return MavenProjectClassName;
		}

	public Object getMavenProject( final Project antProject) {
		if ( antProject == null) {
			throw new IllegalArgumentException( "Value of antProject input cannot be null");
		}
		
		Object mavenProjectObj = antProject.getReference( MavenProjectAccessor.getMavenProjectReferenceId());
			if ( mavenProjectObj == null) {
				String mavenAntrunPluginJarPath 
				= antProject.getProperty( 
						MavenProjectAccessor.getMavenAntrunPluginArtifactJarDesignation());
				if ( mavenAntrunPluginJarPath != null) {
					StringBuffer stringBuffer = new StringBuffer();
						stringBuffer.append( 
								"\r\n\thttps://svn.apache.org/viewvc/maven/plugins/tags" 
								+ "/maven-antrun-plugin-1.7/src/main/java/org/apache/maven" 
								+ "/ant/tasks/AttachArtifactTask.java?view=markup");
						stringBuffer.append( 
								"\r\n\thttps://svn.apache.org/viewvc/maven/plugins/tags" 
								+ "/maven-antrun-plugin-1.7/src/main/java/org/apache/maven" 
								+ "/plugin/antrun/AntRunMojo.java?view=markup");
					throw new BuildException( 
							String.format(
									"Could not locate reference to MavenProject object. The reference key " 
									+ "for MavenProject object may have been changed to something else " 
									+ "from \"%1$s\". Please check changes on %2$s class of " 
									+ "maven-antrun-plugin. You may use these references for further " 
									+ "analysis: ",
									MavenProjectReferenceId, 
									"AttachArtifactTask",
									stringBuffer.toString()
									)
							);
				}
			}
		
		if ( mavenProjectObj != null) {
			Class<?> mavenProjectClass = mavenProjectObj.getClass();
			if ( !MavenProjectAccessor.getMavenProjectClassName().equals( mavenProjectClass.getName())) {
				Logger logger = getLogger();
					if ( logger.isWarnEnabled()) {
						logger.warn( 
								String.format(
										"Though obtained %1$s by referencing \"%2$s\" as ID out of %3$s, " 
										+ "its class name did not match to %4$s known as MavenProject " 
										+ "type. There might have been change on MavenProject class " 
										+ "since development of %5$s class.",
										mavenProjectObj.toString(),
										MavenProjectAccessor.getMavenProjectReferenceId(), 
										antProject.toString(), 
										MavenProjectAccessor.getMavenProjectClassName(), 
										this.getClass().getName()
										)
								);
					}
			}
		}
			
		return mavenProjectObj;
	}
	/**
	 * Get {@link MavenProject} object by referencing {@link #MavenProjectReferenceId} as ID 
	 * out of <code>projectComponent</code> input
	 * @param projectComponent to locate <code>MavenProject</code> object from 
	 * @return <code>MavenProject</code> object or null
	 */
	public Object getMavenProject( final ProjectComponent projectComponent) {
		if ( projectComponent == null) {
			throw new IllegalArgumentException( "Value of projectComponent input cannot be null");
		}
		
		Project antProject = projectComponent.getProject();
		return getMavenProject( antProject);
	}
	
	public static final String GetNameMethodName = "getName";
		public static String getGetNameMethodName() {
			return GetNameMethodName;
		}
	public String getMavenProjectName( final Object mavenProjectObj) {
		if ( mavenProjectObj == null) return null;
		
		Class<?> mavenProjectClass = mavenProjectObj.getClass();
		try {
			Method getNameMethod 
			= mavenProjectClass.getMethod( 
					MavenProjectAccessor.getGetNameMethodName(), (Class<?>[])null);
				getNameMethod.setAccessible( true);
			if ( 
					!String.class.isAssignableFrom( getNameMethod.getReturnType())
					|| (getNameMethod.getParameterTypes().length != 0)) 
			{
				throw new BuildException(
						String.format(
								"The signature of %1$s method on %2$s is different from expected one; " 
								+ "expected return type: %3$s and expected no parameter.",
								MavenProjectAccessor.getGetNameMethodName(),
								mavenProjectObj.toString(),
								String.class.getName()
								)
						);
			}
			return (String)(getNameMethod.invoke( mavenProjectObj, (Object[])null));
		}
		catch( Exception exception) {
			if ( exception instanceof BuildException) throw (BuildException)exception;
			throw new BuildException(
					String.format(
							"Failure in getting name of Maven project out of %1$s.",
							mavenProjectObj.toString()
							),
					exception
					);
		}
	}
	public String getMavenProjectName( final ProjectComponent projectComponent) {
		Object mavenProjectObj = getMavenProject( projectComponent);
		return getMavenProjectName( mavenProjectObj);
	}
		
	public static final String GetPropertiesMethodName = "getProperties";
		/**
		 * @return {@value #GetPropertiesMethodName}
		 */
		public static String getGetPropertiesMethodName() {
			return GetPropertiesMethodName;
		}
	public Properties getMavenProperties( final Object mavenProjectObj) {
		if ( mavenProjectObj == null) return null;
		
		Class<?> mavenProjectClass = mavenProjectObj.getClass();
		try {
			Method getPropertiesMethod 
			= mavenProjectClass.getMethod( 
					MavenProjectAccessor.getGetPropertiesMethodName(), (Class<?>[])null);
				getPropertiesMethod.setAccessible( true);
			if ( 
					!Properties.class.isAssignableFrom( getPropertiesMethod.getReturnType())
					|| (getPropertiesMethod.getParameterTypes().length != 0)) 
			{
				throw new BuildException(
						String.format(
								"The signature of %1$s method on %2$s is different from expected one; " 
								+ "expected return type: %3$s and expected no parameter.",
								MavenProjectAccessor.getGetPropertiesMethodName(),
								mavenProjectObj.toString(),
								Properties.class.getName()
								)
						);
			}
			return (Properties)(getPropertiesMethod.invoke( mavenProjectObj, (Object[])null));
		}
		catch( Exception exception) {
			if ( exception instanceof RuntimeException) throw (RuntimeException)exception;
			throw new BuildException(
					String.format(
							"Failure in getting Maven properties out of %1$s.",
							mavenProjectObj.toString()
							),
					exception
					);
		}
	}
	public Properties getMavenProperties( final Project antProject) {
		Object mavenProjectObj = getMavenProject( antProject);
		return getMavenProperties( mavenProjectObj);
	}
	public Properties getMavenProperties( final ProjectComponent projectComponent) {
		Object mavenProjectObj = getMavenProject( projectComponent);
		return getMavenProperties( mavenProjectObj);
	}
	
	public String getMavenProperty( final Project antProject, final String key) {
		Properties mavenProperties = getMavenProperties( antProject);
			if ( mavenProperties == null) return null;
		return mavenProperties.getProperty( key);
	}
	public String getMavenProperty( final ProjectComponent projectComponent, final String key) {
		Properties mavenProperties = getMavenProperties( projectComponent);
			if ( mavenProperties == null) return null;
		return mavenProperties.getProperty( key);
	}
}
